using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
using System.ServiceModel.Channels;
using WcfContrib.Exceptions;
using WcfContrib.Resources;

namespace WcfContrib.Extensions
{
    /// <summary>
    /// Represents the base class for implementing custom logic upon operation invocation.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public abstract class OperationCallActionAttribute : Attribute, IOperationBehavior, IParameterInspector
    {
        #region Input Handling
        /// <summary>
        /// Gets the first assignable input parameter
        /// </summary>
        /// <typeparam name="T">The input parameter assignable type</typeparam>
        /// <param name="inputs">The input parameters</param>
        /// <param name="value">The input value</param>
        /// <returns>Whether the input was found</returns>
        protected bool TryGetInput<T>(object[] inputs, out T value)
        {
            return TryGetInput<T>(inputs, -1, out value);
        }

        /// <summary>
        /// Gets an input parameter in a given index
        /// </summary>
        /// <typeparam name="T">The input parameter assignable type</typeparam>
        /// <param name="inputs">The input parameters</param>
        /// <param name="index">The parameter index</param>
        /// <param name="value">The input value</param>
        /// <returns>Whether the input was found</returns>
        protected bool TryGetInput<T>(object[] inputs, int index, out T value)
        {
            return TryGetInput<T>(inputs, index, 1, out value);
        }

        /// <summary>
        /// Gets an input parameter starting from the specified index until the occurence count match
        /// </summary>
        /// <typeparam name="T">The input parameter assignable type</typeparam>
        /// <param name="inputs">The input parameters</param>
        /// <param name="index">The parameter index</param>
        /// <param name="occurrence">The occurence count</param>
        /// <param name="value">The input value</param>
        /// <returns>Whether the input was found</returns>
        protected bool TryGetInput<T>(object[] inputs, int index, int occurrence, out T value)
        {
            bool match = false;

            value = default(T);

            if (index >= 0)
            {
                if (index < inputs.Length && inputs[index] is T)
                {
                    value = (T)inputs[index];

                    match = true;
                }
            }
            else
            {
                Predicate<object> matchPred = delegate(object e)
                {
                    return (e is T);
                };

                object[] found = Array.FindAll(inputs, matchPred);

                if (found != null && found.Length >= occurrence)
                {
                    value = (T)found[occurrence - 1];

                    match = true;
                }
            }

            return match;
        }
        #endregion

        #region Execute
        /// <summary>
        /// Returns the invalid exception to be thrown to the client.
        /// </summary>
        /// <returns>The exception</returns>
        protected virtual Exception GetInvalidException()
        {
            return new FaultExceptionBase(ExceptionMessages.OperationCallActionAttribute_Invalid);
        }

        /// <summary>'
        /// Load context if necessary, returns the context that was loaded to be sent to the execute operation.
        /// Setting isValid to false will cause an invalid exception to be thrown back to the client
        /// </summary>
        /// <param name="operationName">The operation name</param>
        /// <param name="inputs">The input parameters</param>
        /// <param name="isValid">Whether the context is valid</param>
        /// <returns></returns>
        protected virtual object LoadContext(string operationName, object[] inputs, out bool isValid)
        {
            isValid = true;
            return null;
        }

        void EnsureAllowed(bool collectInfo, string operationName, object[] inputs)
        {
            object context = null;
            
            if (collectInfo)
            {
                bool isValid;
                context = LoadContext(operationName, inputs, out isValid);

                if (!isValid)
                {
                    throw GetInvalidException();
                }
            }

            Execute(operationName, inputs, context);
        }

        /// <summary>
        /// Execute custom action
        /// </summary>
        /// <param name="operationName">The operation name</param>
        /// <param name="inputs">The input parameters</param>
        /// <param name="context">The context that was returned from the context loading method</param>
        protected abstract void Execute(string operationName, object[] inputs, object context);
        #endregion

        #region IOperationBehavior Members
        void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
        }

        void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
        }

        void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.ParameterInspectors.Add(this);
        }

        void IOperationBehavior.Validate(OperationDescription operationDescription)
        {
        }
        #endregion

        #region IParameterInspector Members
        void IParameterInspector.AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
        {
        }

        object IParameterInspector.BeforeCall(string operationName, object[] inputs)
        {
            EnsureAllowed(true, operationName, inputs);

            return null;
        }
        #endregion
    }
}